package jmine.tec.rpc.server.representation;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Contexto de Representação
 * 
 * @author lundberg
 */
public class RepresentationContext {

    private final BaseRepresentationFactory factory;

    private final Map<MapKey, Object> representationMap = new HashMap<MapKey, Object>();

    /**
     * Construtor
     * 
     * @param factory factory
     */
    public RepresentationContext(BaseRepresentationFactory factory) {
        this.factory = factory;
    }

    /**
     * Registra a relação entre um objeto e sua representação
     * 
     * @param object object
     * @param representation representation
     */
    public void register(Object object, Object representation) {
        this.representationMap.put(this.getMapKey(representation.getClass(), object), representation);
    }

    /**
     * Cria uma representação do objeto, de acordo com o tipo de representação desejada.
     * 
     * @param <R> tipo de representação
     * @param <E> tipo de objeto
     * @param representationClass representationClass
     * @param object object
     * @return representation
     */
    public <R, E> R representObject(Class<R> representationClass, E object) {
        MapKey key = this.getMapKey(representationClass, object);
        if (this.representationMap.containsKey(key)) {
            return representationClass.cast(this.representationMap.get(key));
        }
        return this.factory.representObject(this, representationClass, object);
    }

    /**
     * @param representationClass representation class.
     * @param object object.
     * @return the map key.
     */
    private MapKey getMapKey(Class representationClass, Object object) {
        return new MapKey(object, representationClass);
    }

    /**
     * Cria representações dos objetos, de acordo com o tipo de representação desejada.
     * 
     * @param <R> tipo de representação
     * @param <E> tipo de objeto
     * @param representationClass representationClass
     * @param objects objects
     * @return representation
     */
    @SuppressWarnings("unchecked")
    public <R, E> List<R> representObjects(Class<R> representationClass, List<E> objects) {
        MapKey key = this.getMapKey(representationClass, objects);
        if (this.representationMap.containsKey(key)) {
            return List.class.cast(this.representationMap.get(key));
        }
        return this.factory.representObjects(this, representationClass, objects);
    }

    /**
     * Cria representações dos objetos, de acordo com o tipo de representação desejada.
     * 
     * @param <R> tipo de representação
     * @param <E> tipo de objeto
     * @param representationClass representationClass
     * @param objects objects
     * @return representation
     */
    @SuppressWarnings("unchecked")
    public <R, E> Set<R> representObjects(Class<R> representationClass, Set<E> objects) {
        MapKey key = this.getMapKey(representationClass, objects);
        if (this.representationMap.containsKey(key)) {
            return Set.class.cast(this.representationMap.get(key));
        }
        return this.factory.representObjects(this, representationClass, objects);
    }

    /**
     * Cria representações dos objetos, de acordo com o tipo de representação desejada.
     * 
     * @param <RK> tipo de representação chave
     * @param <RV> tipo de representação valor
     * @param <EK> tipo de objeto chave
     * @param <EV> tipo de objeto valor
     * @param keyClass keyClass
     * @param valueClass valueClass
     * @param objects objects
     * @return representation
     */
    @SuppressWarnings("unchecked")
    public <RK, RV, EK, EV> Map<RK, RV> representObjects(Class<RK> keyClass, Class<RV> valueClass, Map<EK, EV> objects) {
        MapKey key = this.getMapKey(objects.getClass(), objects);
        if (this.representationMap.containsKey(key)) {
            return Map.class.cast(this.representationMap.get(key));
        }
        return this.factory.representObjects(this, keyClass, valueClass, objects);
    }

    /**
     * VO para armazenar o valor no representation map. Ele, propositalmente, faz o equals fazer o == para o objeto dentro dela.
     * 
     * @author wagner.tsuchiya
     */
    static class MapKey {
        private Object object;

        private Class klass;

        /**
         * @param object object.
         * @param klass classe.
         */
        public MapKey(Object object, Class klass) {
            this.object = object;
            this.klass = klass;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (this.klass == null ? 0 : this.klass.hashCode());
            result = prime * result + (this.object == null ? 0 : this.object.hashCode());
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (this.getClass() != obj.getClass()) {
                return false;
            }
            MapKey other = (MapKey) obj;
            if (this.klass == null) {
                if (other.klass != null) {
                    return false;
                }
            } else if (!this.klass.equals(other.klass)) {
                return false;
            }
            if (this.object == null) {
                if (other.object != null) {
                    return false;
                }
            } else if (this.object != other.object) {
                return false;
            }
            return true;
        }
    }
}
